<script setup lang="ts">
  import { reactive, ref, onMounted } from 'vue';
  import wallpaperImg from '@/assets/images/wallpaper.jpg';

  const props = defineProps({
    // 验证码宽度
    width: {
      type: Number,
      default: 500,
    },
    // 验证码高度
    height: {
      type: Number,
      default: 300,
    },
  });

  function withPx(value: number) {
    return `${value}px`;
  }

  const { width, height } = props;

  const wrapperWidth = withPx(width);
  const wrapperHeight = withPx(height);

  const model = defineModel<boolean>();

  const emits = defineEmits(['success']);

  const canvasBg = ref<HTMLCanvasElement>();

  const canvasFronted = ref<HTMLCanvasElement>();

  const imgValue = ref<HTMLImageElement>();

  const sliderPosition = reactive({
    x: 0,
  });

  const leftValue = ref(0);

  const floatTopOffset = ref(100);

  const floatLeftOffset = ref(220);

  // 正方形边长
  const l = 42;
  // 小圆半径
  const r = 9;

  const size = 50;

  const L = size + r * 2;

  const clipImg = ref();

  const init = () => {
    const img = new Image();
    img.src = wallpaperImg;
    img.onload = () => {
      imgValue.value = img;
      drawFloatImg();
    };
  };

  onMounted(() => {
    init();
  });

  function onSliderMove(params: any) {
    const { clientX } = params;
    const { x } = sliderPosition;

    const left = clientX - x;

    const maxLeft = width - (l + r * 2);

    if (left > 0) {
      // @ts-ignore
      leftValue.value = Number(left.toFixed(0));
    } else {
      leftValue.value = 0;
    }

    if (left > maxLeft) {
      leftValue.value = maxLeft;
    }
  }

  function resetSlide() {
    const interval = setInterval(() => {
      leftValue.value -= 10;
      if (leftValue.value < 0) {
        clearInterval(interval);
        leftValue.value = 0;
      }
    }, 10);
  }

  function onSliderUp() {
    window.onmousemove = null;
    window.onmouseup = null;
    checkVerify();
  }

  function onSliderDown(params: any) {
    sliderPosition.x = params.clientX;
    window.onmousemove = onSliderMove;
    window.onmouseup = onSliderUp;
  }

  const drawPath = (
    ctx: CanvasRenderingContext2D,
    x: number,
    y: number,
    operation: 'fill' | 'clip'
  ) => {
    const PI = Number(Math.PI);

    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
    ctx.lineTo(x + l, y);
    ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
    ctx.lineTo(x + l, y + l);
    ctx.lineTo(x, y + l);
    ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
    ctx.lineTo(x, y);
    ctx.lineWidth = 2;
    ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
    ctx.strokeStyle = 'rgba(255, 255, 255, 0.7)';
    ctx.stroke();

    if (operation === 'fill') {
      ctx.globalCompositeOperation = 'destination-in';
    } else {
      ctx.globalCompositeOperation = 'destination-out';
    }
    ctx.fill();
  };

  function drawFloatImg() {
    const canvas: HTMLCanvasElement | undefined = canvasBg.value;

    if (!canvas) {
      return;
    }

    const ctx = canvas.getContext('2d');

    if (!ctx) {
      return;
    }

    const top = floatTopOffset.value;

    const img = imgValue.value;

    if (!img) {
      return;
    }

    ctx.drawImage(img, 0, 0, width, height);

    clipImg.value = ctx.getImageData(
      floatLeftOffset.value,
      top - r * 2 - 1,
      size + r * 2,
      size + r * 2
    );

    drawPath(ctx, floatLeftOffset.value, top, 'clip');

    drawSlide();
  }

  function drawSlide() {
    const canvas: HTMLCanvasElement | undefined = canvasFronted.value;

    if (!canvas) {
      return;
    }

    const ctx = canvas.getContext('2d');

    if (!ctx) {
      return;
    }

    const top = floatTopOffset.value;

    canvas.width = L;

    ctx.clearRect(0, 0, L, height);

    ctx.putImageData(clipImg.value, 0, top - r * 2 - 1);

    drawPath(ctx, 0, top, 'fill');
  }

  const status = ref<'loading' | 'success' | 'fail' | 'normal'>('normal');

  function checkVerify() {
    const offset = floatLeftOffset.value;
    const left = leftValue.value;

    const value = offset - left;

    if (Math.abs(value) < 20) {
      leftValue.value = offset;
      status.value = 'success';
      model.value = true;
      emits('success');
    } else {
      status.value = 'fail';
    }
  }

  function refreshVerify() {
    status.value = 'normal';
    resetSlide();
  }
</script>

<template>
  <div class="col sv-wrapper">
    <div class="canvas-wrapper">
      <canvas
        id="canvasBg"
        ref="canvasBg"
        :width="wrapperWidth"
        :height="wrapperHeight"
      >
      </canvas>
      <canvas
        id="canvasFronted"
        ref="canvasFronted"
        :width="wrapperWidth"
        :height="wrapperHeight"
        :style="{ left: leftValue + 'px' }"
      >
      </canvas>
    </div>
    <div class="slider-wrapper">
      <div
        v-if="status === 'normal'"
        class="slider"
        :style="{ left: leftValue + 'px' }"
        @mousedown="onSliderDown"
      ></div>

      <div v-else-if="status === 'success'" class="success result">
        <icon-check />
        验证成功
      </div>

      <div v-else-if="status === 'loading'" class="loading result">
        <icon-loading />
        加载中...
      </div>

      <div v-else-if="status === 'fail'" class="fail result">
        <icon-info-circle />
        <span @click="refreshVerify"> 验证失败，点击重试</span>
      </div>
    </div>
  </div>
</template>

<style lang="less" scoped>
  .sv-wrapper {
    user-select: none;

    .canvas-wrapper {
      width: v-bind(wrapperWidth);
      height: v-bind(wrapperHeight);
      position: relative;
      canvas {
        position: absolute;
        top: 0;
        left: 0;
      }

      #canvasBg {
        z-index: 1;
      }

      #canvasFronted {
        z-index: 2;
      }
    }

    .slider-wrapper {
      width: v-bind(wrapperWidth);
      height: 32px;
      position: relative;
      border: 1px solid #dcdcdc;
      padding: 2px;

      .slider {
        width: 50px;
        height: 28px;
        position: absolute;
        top: 2px;
        cursor: all-scroll;
        background-image: url(../../assets/svg/arrow-right.svg);
        background-position: center center;
        background-repeat: no-repeat;
        background-size: 70px 20px;
        background-color: #f5f5f5;
      }

      .result {
        width: 100%;
        height: 100%;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 10px;
      }

      .success {
        background-color: #e8ffea;
        color: #009a29;
      }

      .fail {
        background-color: #ffece8;
        color: #cb2634;

        span {
          cursor: pointer;
        }
      }
      .loading {
        background-color: #f7f8fa;
        color: #1d2129;
      }
    }
  }
</style>
